<exclude-pattern>*/includes/libs/filebackend/SwiftFileBackend\.php</exclude-pattern>
<exclude-pattern>*/includes/logging/LogEntry\.php</exclude-pattern>
<exclude-pattern>*/includes/logging/LogFormatter\.php</exclude-pattern>
- <exclude-pattern>*/includes/media/MediaTransformOutput\.php</exclude-pattern>
<exclude-pattern>*/includes/media/SVGMetadataExtractor\.php</exclude-pattern>
<exclude-pattern>*/includes/parser/Preprocessor_DOM\.php</exclude-pattern>
<exclude-pattern>*/includes/parser/Preprocessor_Hash\.php</exclude-pattern>
<exclude-pattern>*/includes/parser/Preprocessor\.php</exclude-pattern>
<exclude-pattern>*/includes/PathRouter\.php</exclude-pattern>
<exclude-pattern>*/includes/profiler/SectionProfiler\.php</exclude-pattern>
- <exclude-pattern>*/includes/search/SearchEngine\.php</exclude-pattern>
<exclude-pattern>*/includes/specialpage/LoginSignupSpecialPage\.php</exclude-pattern>
<exclude-pattern>*/includes/specials/forms/PreferencesFormLegacy\.php</exclude-pattern>
<exclude-pattern>*/includes/StubObject\.php</exclude-pattern>
Content::getNativeData() for text-based content models.
* (T214706) LinksUpdate::getAddedExternalLinks() and
LinksUpdate::getRemovedExternalLinks() were introduced.
+* (T213893) Added 'MaintenanceUpdateAddParams' hook
=== External library changes in 1.33 ===
==== New external libraries ====
to "PÃ gina printzipale". Existing wikis using this content language need to
move the main page or change the name through MediaWiki:Mainpage page.
* wfSplitWikiID(), deprecated in 1.32, has been removed.
+* MessageBlobStore::getBlob(), deprecated in 1.27, has been removed.
+ Use ::getBlobs() instead.
=== Deprecations in 1.33 ===
* The configuration option $wgUseESI has been deprecated, and is expected
'MediaHandler' => __DIR__ . '/includes/media/MediaHandler.php',
'MediaHandlerFactory' => __DIR__ . '/includes/media/MediaHandlerFactory.php',
'MediaStatisticsPage' => __DIR__ . '/includes/specials/SpecialMediaStatistics.php',
- 'MediaTransformError' => __DIR__ . '/includes/media/MediaTransformOutput.php',
+ 'MediaTransformError' => __DIR__ . '/includes/media/MediaTransformError.php',
'MediaTransformInvalidParametersException' => __DIR__ . '/includes/media/MediaTransformInvalidParametersException.php',
'MediaTransformOutput' => __DIR__ . '/includes/media/MediaTransformOutput.php',
'MediaWiki' => __DIR__ . '/includes/MediaWiki.php',
'SearchDump' => __DIR__ . '/maintenance/dumpIterator.php',
'SearchEngine' => __DIR__ . '/includes/search/SearchEngine.php',
'SearchEngineConfig' => __DIR__ . '/includes/search/SearchEngineConfig.php',
- 'SearchEngineDummy' => __DIR__ . '/includes/search/SearchEngine.php',
+ 'SearchEngineDummy' => __DIR__ . '/includes/search/SearchEngineDummy.php',
'SearchEngineFactory' => __DIR__ . '/includes/search/SearchEngineFactory.php',
'SearchExactMatchRescorer' => __DIR__ . '/includes/search/SearchExactMatchRescorer.php',
'SearchHighlighter' => __DIR__ . '/includes/search/SearchHighlighter.php',
'TextStatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
'TgConverter' => __DIR__ . '/languages/classes/LanguageTg.php',
'ThrottledError' => __DIR__ . '/includes/exception/ThrottledError.php',
- 'ThumbnailImage' => __DIR__ . '/includes/media/MediaTransformOutput.php',
+ 'ThumbnailImage' => __DIR__ . '/includes/media/ThumbnailImage.php',
'ThumbnailRenderJob' => __DIR__ . '/includes/jobqueue/jobs/ThumbnailRenderJob.php',
'TidyUpT39714' => __DIR__ . '/maintenance/tidyUpT39714.php',
'TiffHandler' => __DIR__ . '/includes/media/TiffHandler.php',
'TrackingCategories' => __DIR__ . '/includes/TrackingCategories.php',
'TraditionalImageGallery' => __DIR__ . '/includes/gallery/TraditionalImageGallery.php',
'TransactionRoundDefiningUpdate' => __DIR__ . '/includes/deferred/TransactionRoundDefiningUpdate.php',
- 'TransformParameterError' => __DIR__ . '/includes/media/MediaTransformOutput.php',
- 'TransformTooBigImageAreaError' => __DIR__ . '/includes/media/MediaTransformOutput.php',
+ 'TransformParameterError' => __DIR__ . '/includes/media/TransformParameterError.php',
+ 'TransformTooBigImageAreaError' => __DIR__ . '/includes/media/TransformTooBigImageAreaError.php',
'TransformationalImageHandler' => __DIR__ . '/includes/media/TransformationalImageHandler.php',
'UDPRCFeedEngine' => __DIR__ . '/includes/rcfeed/UDPRCFeedEngine.php',
'UDPTransport' => __DIR__ . '/includes/libs/UDPTransport.php',
'MagicWordwgVariableIDs': When defining new magic words IDs.
&$variableIDs: array of strings
+'MaintenanceUpdateAddParams': allow extensions to add params to the update.php
+maintenance script.
+&$params: array to populate with the params to be added. Array elements are keyed by
+the param name. Each param is an associative array that must include the following keys:
+ - desc The description of the param to show on --help
+ - require Is the param required? Defaults to false if not set.
+ - withArg Is an argument required with this option? Defaults to false if not set.
+ - shortName Character to use as short name, or false if none. Defaults to false if not set.
+ - multiOccurrence Can this option be passed multiple times? Defaults to false if not set.
+
'MaintenanceRefreshLinksInit': before executing the refreshLinks.php maintenance
script.
$refreshLinks: RefreshLinks object
// their own talk page unless a restriction exists on the
// page or User_talk: namespace
wfSetVar( $this->allowUsertalk, $x === null ? null : !$x );
- $res = !$this->isUserTalkEditAllowed();
+ $res = !$this->isUsertalkEditAllowed();
// edit own user talk can be disabled by config
if ( !$blockAllowsUTEdit ) {
* - a) Whether it is fully qualified or wiki-relative.
* By default, the paths of files are relative to the current wiki,
* which works via prefixing them with the current wiki ID when accessed.
- * Setting 'wikiId' forces the backend to be fully qualified by prefixing
+ * Setting 'domainId' forces the backend to be fully qualified by prefixing
* all paths with the specified value instead. This can be useful if
* multiple wikis need to share the same data. Note that 'name' is *not*
* part of any prefix and thus should not be relied upon for namespacing.
} elseif ( $this->section == 'new' ) {
// Nothing *to* preview for new sections
return false;
- } elseif ( ( $request->getVal( 'preload' ) !== null || $this->mTitle->exists() )
+ } elseif ( ( $request->getCheck( 'preload' ) || $this->mTitle->exists() )
&& $this->context->getUser()->getOption( 'previewonfirst' )
) {
// Standard preference behavior
$this->scrolltop = $request->getIntOrNull( 'wpScrolltop' );
- if ( $this->textbox1 === '' && $request->getVal( 'wpTextbox1' ) === null ) {
+ if ( $this->textbox1 === '' && !$request->getCheck( 'wpTextbox1' ) ) {
// wpTextbox1 field is missing, possibly due to being "too big"
// according to some filter rules such as Suhosin's setting for
// suhosin.request.max_value_length (d'oh)
if ( $request->getVal( 'action', 'view' ) != 'view'
|| $request->wasPosted()
- || ( $request->getVal( 'title' ) !== null
+ || ( $request->getCheck( 'title' )
&& $title->getPrefixedDBkey() == $request->getVal( 'title' ) )
|| count( $request->getValueNames( [ 'action', 'title' ] ) )
|| !Hooks::run( 'TestCanonicalRedirect', [ $request, $title, $output ] )
// And we have to use raw SQL to bypass the "aggregation used with a locking SELECT" warning
// that's for non-MySQL DBs.
$row1 = $dbw->query(
- $dbw->selectSqlText( 'archive', [ 'v' => "MAX(ar_rev_id)" ], '', __METHOD__ ) . ' FOR UPDATE'
+ $dbw->selectSQLText( 'archive', [ 'v' => "MAX(ar_rev_id)" ], '', __METHOD__ ) . ' FOR UPDATE'
)->fetchObject();
if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) {
$row2 = $dbw->query(
- $dbw->selectSqlText( 'slots', [ 'v' => "MAX(slot_revision_id)" ], '', __METHOD__ )
+ $dbw->selectSQLText( 'slots', [ 'v' => "MAX(slot_revision_id)" ], '', __METHOD__ )
. ' FOR UPDATE'
)->fetchObject();
} else {
private $tableCache = null;
/** @var bool|string */
- private $wikiId = false;
+ private $domain = false;
/** @var int */
private $cacheTTL;
* @param string $nameField
* @param callable|null $normalizationCallback Normalization to be applied to names before being
* saved or queried. This should be a callback that accepts and returns a single string.
- * @param bool|string $wikiId The ID of the target wiki database. Use false for the local wiki.
+ * @param bool|string $dbDomain Database domain ID. Use false for the local database domain.
* @param callable|null $insertCallback Callback to change insert fields accordingly.
* This parameter was introduced in 1.32
*/
$idField,
$nameField,
callable $normalizationCallback = null,
- $wikiId = false,
+ $dbDomain = false,
callable $insertCallback = null
) {
$this->loadBalancer = $dbLoadBalancer;
$this->idField = $idField;
$this->nameField = $nameField;
$this->normalizationCallback = $normalizationCallback;
- $this->wikiId = $wikiId;
+ $this->domain = $dbDomain;
$this->cacheTTL = IExpiringStore::TTL_MONTH;
$this->insertCallback = $insertCallback;
}
* @return IDatabase
*/
private function getDBConnection( $index, $flags = 0 ) {
- return $this->loadBalancer->getConnection( $index, [], $this->wikiId, $flags );
+ return $this->loadBalancer->getConnection( $index, [], $this->domain, $flags );
}
/**
return $this->cache->makeGlobalKey(
'NameTableSqlStore',
$this->table,
- $this->loadBalancer->resolveDomainID( $this->wikiId )
+ $this->loadBalancer->resolveDomainID( $this->domain )
);
}
$dbw->onTransactionPreCommitOrIdle(
function () use ( $dbw ) {
ResourceLoaderWikiModule::invalidateModuleCache(
- $this, null, null, $dbw->getDomainId() );
+ $this, null, null, $dbw->getDomainID() );
},
__METHOD__
);
/**
* Get the wiki ID of a database domain
*
- * This is like DatabaseDomain::getId() without encoding (for legacy reasons)
- * and without the schema if it merely set to the generic value "mediawiki"
+ * This is like DatabaseDomain::getId() without encoding (for legacy reasons) and
+ * without the schema if it is the generic installer default of "mediawiki"/"dbo"
+ *
+ * @see $wgDBmwschema
+ * @see PostgresInstaller
+ * @see MssqlInstaller
*
* @param string|DatabaseDomain $domain
* @return string
*/
public static function getWikiIdFromDbDomain( $domain ) {
$domain = DatabaseDomain::newFromId( $domain );
-
+ // Since the schema was not always part of the wiki ID, try to maintain backwards
+ // compatibility with some common cases. Assume that if the DB domain schema is just
+ // the installer default then it is probably the case that the schema is the same for
+ // all wikis in the farm. Historically, any wiki farm had to make the database/prefix
+ // combination unique per wiki. Ommit the schema if it does not seem wiki specific.
if ( !in_array( $domain->getSchema(), [ null, 'mediawiki', 'dbo' ], true ) ) {
- // Include the schema if it is set and is not the default placeholder.
// This means a site admin may have specifically taylored the schemas.
- // Domain IDs might use the form <DB>-<project>-<language>, meaning that
- // the schema portion must be accounted for to disambiguate wikis.
+ // Domain IDs might use the form <DB>-<project>- or <DB>-<project>-<language>_,
+ // meaning that the schema portion must be accounted for to disambiguate wikis.
return "{$domain->getDatabase()}-{$domain->getSchema()}-{$domain->getTablePrefix()}";
}
-
// Note that if this wiki ID is passed a a domain ID to LoadBalancer, then it can
// handle the schema by assuming the generic "mediawiki" schema if needed.
return strlen( $domain->getTablePrefix() )
/**
* @param DatabaseDomain|string $domain
- * @return bool Whether $domain has the same DB/prefix as the current wiki
+ * @return bool Whether $domain matches the DB domain of the current wiki
* @since 1.33
*/
public static function isCurrentWikiDbDomain( $domain ) {
- $domain = DatabaseDomain::newFromId( $domain );
- $curDomain = self::getCurrentWikiDbDomain();
-
- if ( !in_array( $curDomain->getSchema(), [ null, 'mediawiki', 'dbo' ], true ) ) {
- // Include the schema if it is set and is not the default placeholder.
- // This means a site admin may have specifically taylored the schemas.
- // Domain IDs might use the form <DB>-<project>-<language>, meaning that
- // the schema portion must be accounted for to disambiguate wikis.
- return $curDomain->equals( $domain );
- }
-
- return (
- $curDomain->getDatabase() === $domain->getDatabase() &&
- $curDomain->getTablePrefix() === $domain->getTablePrefix()
- );
+ return self::getCurrentWikiDbDomain()->equals( DatabaseDomain::newFromId( $domain ) );
}
/**
* @param string $wikiId
- * @return bool Whether $wikiId has the same DB/prefix as the current wiki
+ * @return bool Whether $wikiId matches the wiki ID of the current wiki
* @since 1.33
*/
public static function isCurrentWikiId( $wikiId ) {
}
// Add the general form.
- $action = htmlspecialchars( wfScript() );
$fields = [
[
'name' => 'title',
$htmlForm = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
$htmlForm
->setMethod( 'get' )
- ->setAction( $action )
+ ->setAction( wfScript() )
->setId( 'mw-history-searchform' )
->setSubmitText( $this->msg( 'historyaction-submit' )->text() )
+ ->setWrapperAttributes( [ 'id' => 'mw-history-search' ] )
->setWrapperLegend( $this->msg( 'history-fieldset-title' )->text() );
$htmlForm->loadData();
'class' => 'mw-content-' . $pageViewLang->getDir() ];
$previewHTML = Html::rawElement( 'div', $attribs, $previewHTML );
- $out->addHtml( $previewhead . $previewHTML );
+ $out->addHTML( $previewhead . $previewHTML );
}
public function onSubmit( $data ) {
"Unsafe JS/CSS/Json {$elevatedText}load - {user} loaded {title} with {ctype}",
[
'user' => $this->getUser()->getName(),
- 'title' => $title->getPrefixedDBKey(),
+ 'title' => $title->getPrefixedDBkey(),
'ctype' => $contentType,
'elevated' => $elevated
]
$log->info( "Blocked loading unprotected JS {title} for {user}",
[
'user' => $this->getUser()->getName(),
- 'title' => $title->getPrefixedDBKey(),
+ 'title' => $title->getPrefixedDBkey(),
]
);
throw new HttpError( 403, wfMessage( 'unprotected-js' ) );
$status = self::delete( $pageObj, $user, $reason, $params['tags'] );
}
- if ( !$status->isOk() ) {
+ if ( !$status->isOK() ) {
$this->dieStatus( $status );
}
$this->addMessagesFromStatus( $status, [ 'warning' ], [ 'delete-scheduled' ] );
'cookies' => $sessionCookies,
'ip' => $request->getIP(),
'userAgent' => $this->getUserAgent(),
- 'wiki' => wfWikiID(),
+ 'wiki' => WikiMap::getCurrentWikiDbDomain()->getId(),
]
);
}
$request = $this->getRequest();
// JSONP mode
- if ( $request->getVal( 'callback' ) !== null ) {
+ if ( $request->getCheck( 'callback' ) ) {
$this->lacksSameOriginSecurity = true;
return true;
}
'ts' => time(),
'ip' => $request->getIP(),
'userAgent' => $this->getUserAgent(),
- 'wiki' => wfWikiID(),
+ 'wiki' => WikiMap::getCurrentWikiDbDomain()->getId(),
'timeSpentBackend' => (int)round( $time * 1000 ),
'hadError' => $e !== null,
'errorCodes' => [],
'method' => $request->getMethod(),
'client_ip' => $request->getIP()
],
- 'database' => wfWikiID(),
+ 'database' => WikiMap::getCurrentWikiDbDomain()->getId(),
'backend_time_ms' => (int)round( $time * 1000 ),
];
$data[ApiResult::META_BC_BOOLS][] = 'variantarticlepath';
$data['server'] = $config->get( 'Server' );
$data['servername'] = $config->get( 'ServerName' );
- $data['wikiid'] = wfWikiID();
+ $data['wikiid'] = WikiMap::getWikiIdFromDbDomain( WikiMap::getCurrentWikiDbDomain() );
$data['time'] = wfTimestamp( TS_ISO_8601, time() );
$data['misermode'] = (bool)$config->get( 'MiserMode' );
$params = $this->extractRequestParams();
$user = $this->getUser();
- if ( $user->isBlocked() ) {
+ $block = $user->getBlock();
+ if ( $block && $block->isSitewide() ) {
$this->dieBlocked( $user->getBlock() );
}
// @codeCoverageIgnoreEnd
}
$this->setDefaultUserOptions( $user, $creator->isAnon() );
- \Hooks::run( 'LocalUserCreated', [ $user, false ] );
+ \Hooks::runWithoutAbort( 'LocalUserCreated', [ $user, false ] );
$user->saveSettings();
$state['userid'] = $user->getId();
"\$wgDBservers has dbname='$srvDB' but \$wgDBname='$ldDB'. " .
"Set \$wgDBname to the database used by this wiki project. " .
"There is rarely a need to set 'dbname' in \$wgDBservers. " .
- "Functions like wfWikiId(), remote wiki database access, the use " .
- "of Database::getDomainId(), and other features are not reliable when " .
+ "Cross-wiki database access, use of WikiMap::getCurrentWikiDbDomain(), " .
+ "use of Database::getDomainId(), and other features are not reliable when " .
"\$wgDBservers does not match the local wiki database/prefix."
);
MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY );
"\$wgDBservers has tablePrefix='$srvTP' but \$wgDBprefix='$ldTP'. " .
"Set \$wgDBprefix to the table prefix used by this wiki project. " .
"There is rarely a need to set 'tablePrefix' in \$wgDBservers. " .
- "Functions like wfWikiId(), remote wiki database access, the use " .
- "of Database::getDomainId(), and other features are not reliable when " .
+ "Cross-wiki database access, use of WikiMap::getCurrentWikiDbDomain(), " .
+ "use of Database::getDomainId(), and other features are not reliable when " .
"\$wgDBservers does not match the local wiki database/prefix."
);
MWExceptionRenderer::output( $e, MWExceptionRenderer::AS_PRETTY );
foreach ( $updates as $update ) {
if ( $update instanceof EnqueueableDataUpdate ) {
$spec = $update->getAsJobSpecification();
- JobQueueGroup::singleton( $spec['wiki'] )->push( $spec['job'] );
+ $domain = $spec['domain'] ?? $spec['wiki'];
+ JobQueueGroup::singleton( $domain )->push( $spec['job'] );
} else {
$remaining[] = $update;
}
$config = $this->backends[$name]['config'];
$config['class'] = $class;
- $config += [ // set defaults
+ if ( isset( $config['domainId'] ) ) {
+ $domain = $config['domainId'];
+ } else {
// @FIXME: this does not include the domain for b/c but it ideally should
- 'wikiId' => wfWikiID(), // e.g. "my_wiki-en_"
+ $domain = $config['wikiId'] ?? wfWikiID();
+ }
+ // Set default parameter values
+ $config += [
+ 'domainId' => $domain, // e.g. "my_wiki-en_"
'mimeCallback' => [ $this, 'guessMimeInternal' ],
'obResetFunc' => 'wfResetOutputBuffers',
'streamMimeFunc' => [ StreamFile::class, 'contentTypeFromPath' ],
}
];
$config['lockManager'] =
- LockManagerGroup::singleton( $config['wikiId'] )->get( $config['lockManager'] );
+ LockManagerGroup::singleton( $domain )->get( $config['lockManager'] );
$config['fileJournal'] = isset( $config['fileJournal'] )
? FileJournal::factory( $config['fileJournal'], $name )
: FileJournal::factory( [ 'class' => NullFileJournal::class ], $name );
class DBFileJournal extends FileJournal {
/** @var IDatabase */
protected $dbw;
-
- protected $wiki = false; // string; wiki DB name
+ /** @var string */
+ protected $domain;
/**
* Construct a new instance from configuration.
*
* @param array $config Includes:
- * 'wiki' : wiki name to use for LoadBalancer
+ * domain: database domain ID of the wiki
*/
protected function __construct( array $config ) {
parent::__construct( $config );
- $this->wiki = $config['wiki'];
+ $this->domain = $config['domain'] ?? $config['wiki']; // b/c
}
/**
if ( !$this->dbw ) {
// Get a separate connection in autocommit mode
$lb = MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->newMainLB();
- $this->dbw = $lb->getConnection( DB_MASTER, [], $this->wiki );
+ $this->dbw = $lb->getConnection( DB_MASTER, [], $this->domain );
$this->dbw->clearFlag( DBO_TRX );
}
/** @var FileBackend $backend */
$backend = $config['backend'];
$config['name'] = $backend->getName();
- $config['wikiId'] = $backend->getWikiId();
+ $config['domainId'] = $backend->getDomainId();
parent::__construct( $config );
$this->backend = $config['backend'];
$this->repoName = $config['repoName'];
}
$this->fileExists = true;
- $this->maybeUpgradeRow();
}
/**
/**
* Upgrade a row if it needs it
*/
- function maybeUpgradeRow() {
+ protected function maybeUpgradeRow() {
global $wgUpdateCompatibleMetadata;
if ( wfReadOnly() || $this->upgrading ) {
*/
function purgeCache( $options = [] ) {
// Refresh metadata cache
+ $this->maybeUpgradeRow();
$this->purgeMetadataCache();
// Delete thumbnails
protected $mButtons = [];
protected $mWrapperLegend = false;
+ protected $mWrapperAttributes = [];
/**
* Salt for the edit token.
# Include a <fieldset> wrapper for style, if requested.
if ( $this->mWrapperLegend !== false ) {
$legend = is_string( $this->mWrapperLegend ) ? $this->mWrapperLegend : false;
- $html = Xml::fieldset( $legend, $html );
+ $html = Xml::fieldset( $legend, $html, $this->mWrapperAttributes );
}
return Html::rawElement(
return $this;
}
+ /**
+ * For internal use only. Use is discouraged, and should only be used where
+ * support for gadgets/user scripts is warranted.
+ * @param array $attributes
+ * @internal
+ * @return HTMLForm $this for chaining calls
+ */
+ public function setWrapperAttributes( $attributes ) {
+ $this->mWrapperAttributes = $attributes;
+
+ return $this;
+ }
+
/**
* Prompt the whole form to be wrapped in a "<fieldset>", with
* this message as its "<legend>" element.
'content' => new OOUI\HtmlSnippet( $html )
] ),
],
- ] );
+ ] + OOUI\Element::configFromHtmlAttributes( $this->mWrapperAttributes ) );
} else {
$content = new OOUI\HtmlSnippet( $html );
}
// Fetch the value in either one of the two following case:
// - we have a valid submit attempt (form was just submitted)
// - we have a value (an URL manually built by the user, or GET form with no wpFormIdentifier)
- if ( $this->isSubmitAttempt( $request ) || $request->getVal( $this->mName ) !== null ) {
+ if ( $this->isSubmitAttempt( $request ) || $request->getCheck( $this->mName ) ) {
return $invert
? !$request->getBool( $this->mName )
: $request->getBool( $this->mName );
}
foreach ( $namespaces as $namespace ) {
+ if ( $namespace < 0 ) {
+ return $this->msg( 'htmlform-select-badoption' );
+ }
+
$result = parent::validate( $namespace, $alldata );
if ( $result !== true ) {
return $result;
protected $mAPI;
/** @var string The name of the database (for a connection to be established
- * with LBFactory::getMainLB( 'wikiid' ))
+ * with LBFactory::getMainLB( 'domainId' ))
*/
protected $mWikiID;
protected $title;
/** @var bool Expensive jobs may set this to true */
- protected $removeDuplicates;
+ protected $removeDuplicates = false;
/** @var string Text for error that occurred last */
protected $error;
* Create the appropriate object to handle a specific job
*
* @param string $command Job command
- * @param Title $title Associated title
* @param array $params Job parameters
* @throws InvalidArgumentException
* @return Job
*/
- public static function factory( $command, Title $title, $params = [] ) {
+ public static function factory( $command, $params = [] ) {
global $wgJobClasses;
+ if ( $params instanceof Title ) {
+ // Backwards compatibility for old signature ($command, $title, $params)
+ $title = $params;
+ $params = func_num_args() >= 3 ? func_get_arg( 2 ) : [];
+ } else {
+ // Subclasses can override getTitle() to return something more meaningful
+ $title = Title::makeTitle( NS_SPECIAL, 'Blankpage' );
+ }
+
if ( isset( $wgJobClasses[$command] ) ) {
$handler = $wgJobClasses[$command];
if ( $job instanceof Job ) {
$job->command = $command;
+
return $job;
} else {
- throw new InvalidArgumentException( "Cannot instantiate job '$command': bad spec!" );
+ throw new InvalidArgumentException( "Could instantiate job '$command': bad spec!" );
}
}
/**
* @param string $command
- * @param Title $title
- * @param array|bool $params Can not be === true
+ * @param array $params
*/
- public function __construct( $command, $title, $params = false ) {
+ public function __construct( $command, $params = [] ) {
+ if ( $params instanceof Title ) {
+ // Backwards compatibility for old signature ($command, $title, $params)
+ $title = $params;
+ $params = func_num_args() >= 3 ? func_get_arg( 2 ) : [];
+ } else {
+ // Subclasses can override getTitle() to return something more meaningful
+ $title = Title::makeTitle( NS_SPECIAL, 'Blankpage' );
+ }
+
$this->command = $command;
$this->title = $title;
$this->params = is_array( $params ) ? $params : []; // sanity
-
- // expensive jobs may set this to true
- $this->removeDuplicates = false;
-
if ( !isset( $this->params['requestId'] ) ) {
$this->params['requestId'] = WebRequest::getRequestId();
}
* @since 1.31
*/
public function hasExecutionFlag( $flag ) {
- return ( $this->executionFlags && $flag ) === $flag;
+ return ( $this->executionFlags & $flag ) === $flag;
}
/**
* Get a job queue object of the specified type.
* $params includes:
* - class : What job class to use (determines job type)
- * - wiki : wiki ID of the wiki the jobs are for (defaults to current wiki)
+ * - domain : Database domain ID of the wiki the jobs are for (defaults to current wiki)
* - type : The name of the job types this queue handles
* - order : Order that pop() selects jobs, one of "fifo", "timestamp" or "random".
* If "fifo" is used, the queue will effectively be FIFO. Note that job
* @deprecated 1.33
*/
final public function getWiki() {
- return $this->domain;
+ return WikiMap::getWikiIdFromDbDomain( $this->domain );
}
/**
$this->coalescedQueues = [];
foreach ( $wgJobTypeConf as $type => $conf ) {
$queue = JobQueue::factory(
- [ 'wiki' => $this->domain, 'type' => 'null' ] + $conf );
+ [ 'domain' => $this->domain, 'type' => 'null' ] + $conf );
$loc = $queue->getCoalesceLocationInternal();
if ( !isset( $this->coalescedQueues[$loc] ) ) {
$this->coalescedQueues[$loc]['queue'] = $queue;
$this->type = $type;
$this->params = $params;
- $this->title = $title ?: Title::makeTitle( NS_SPECIAL, 'Badtitle/' . static::class );
+ $this->title = $title ?: Title::makeTitle( NS_SPECIAL, 'Blankpage' );
$this->opts = $opts;
}
public static function newFromLocalJobs( $jobs ) {
$jobs = is_array( $jobs ) ? $jobs : [ $jobs ];
- return self::newFromJobsByWiki( [ wfWikiID() => $jobs ] );
+ return self::newFromJobsByDomain( [
+ WikiMap::getCurrentWikiDbDomain()->getId() => $jobs
+ ] );
}
/**
- * @param array $jobsByWiki Map of (wiki => JobSpecification list)
+ * @param array $jobsByDomain Map of (wiki => JobSpecification list)
* @return EnqueueJob
*/
- public static function newFromJobsByWiki( array $jobsByWiki ) {
+ public static function newFromJobsByDomain( array $jobsByDomain ) {
$deduplicate = true;
- $jobMapsByWiki = [];
- foreach ( $jobsByWiki as $wiki => $jobs ) {
- $jobMapsByWiki[$wiki] = [];
+ $jobMapsByDomain = [];
+ foreach ( $jobsByDomain as $domain => $jobs ) {
+ $jobMapsByDomain[$domain] = [];
foreach ( $jobs as $job ) {
if ( $job instanceof JobSpecification ) {
- $jobMapsByWiki[$wiki][] = $job->toSerializableArray();
+ $jobMapsByDomain[$domain][] = $job->toSerializableArray();
} else {
throw new InvalidArgumentException( "Jobs must be of type JobSpecification." );
}
$eJob = new self(
Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __CLASS__ ),
- [ 'jobsByWiki' => $jobMapsByWiki ]
+ [ 'jobsByDomain' => $jobMapsByDomain ]
);
// If *all* jobs to be pushed are to be de-duplicated (a common case), then
// de-duplicate this whole job itself to avoid build up in high traffic cases
return $eJob;
}
+ /**
+ * @param array $jobsByWiki
+ * @return EnqueueJob
+ * @deprecated Since 1.33; use newFromJobsByDomain()
+ */
+ public static function newFromJobsByWiki( array $jobsByWiki ) {
+ return self::newFromJobsByDomain( $jobsByWiki );
+ }
+
public function run() {
- foreach ( $this->params['jobsByWiki'] as $wiki => $jobMaps ) {
+ $jobsByDomain = $this->params['jobsByDomain'] ?? $this->params['jobsByWiki']; // b/c
+
+ foreach ( $jobsByDomain as $domain => $jobMaps ) {
$jobSpecs = [];
foreach ( $jobMaps as $jobMap ) {
$jobSpecs[] = JobSpecification::newFromArray( $jobMap );
}
- JobQueueGroup::singleton( $wiki )->push( $jobSpecs );
+ JobQueueGroup::singleton( $domain )->push( $jobSpecs );
}
return true;
!( isset( $params['pages'] ) && count( $params['pages'] ) != 1 )
);
$this->params += [ 'causeAction' => 'unknown', 'causeAgent' => 'unknown' ];
+ // This will control transaction rounds in order to run DataUpdates
+ $this->executionFlags |= self::JOB_NO_EXPLICIT_TRX_ROUND;
}
/**
$renderer = $services->getRevisionRenderer();
$ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+ $lbFactory->beginMasterChanges( __METHOD__ );
+
$page = WikiPage::factory( $title );
$page->loadPageData( WikiPage::READ_LATEST );
$options['triggeringUser'] = User::newFromName( $userInfo['userName'], false );
}
}
+
+ $lbFactory->commitMasterChanges( __METHOD__ );
+
$page->doSecondaryDataUpdates( $options );
InfoAction::invalidateCache( $title );
/**
* This is a wrapper for APC's shared memory functions
*
+ * Use PHP serialization to avoid bugs and easily create CAS tokens.
+ * APCu has a memory corruption bug when the serializer is set to 'default'.
+ * See T120267, and upstream bug reports:
+ * - https://github.com/krakjoe/apcu/issues/38
+ * - https://github.com/krakjoe/apcu/issues/35
+ * - https://github.com/krakjoe/apcu/issues/111
+ *
* @ingroup Cache
*/
class APCBagOStuff extends BagOStuff {
-
- /**
- * @var bool If true, trust the APC implementation to serialize and
- * deserialize objects correctly. If false, (de-)serialize in PHP.
- */
- protected $nativeSerialize;
-
/**
* @var string String to append to each APC key. This may be changed
* whenever the handling of values is changed, to prevent existing code
* from encountering older values which it cannot handle.
*/
- const KEY_SUFFIX = ':2';
-
- /**
- * Available parameters are:
- * - nativeSerialize: If true, pass objects to apc_store(), and trust it
- * to serialize them correctly. If false, serialize
- * all values in PHP.
- *
- * @param array $params
- */
- public function __construct( array $params = [] ) {
- parent::__construct( $params );
-
- if ( isset( $params['nativeSerialize'] ) ) {
- $this->nativeSerialize = $params['nativeSerialize'];
- } elseif ( extension_loaded( 'apcu' ) && ini_get( 'apc.serializer' ) === 'default' ) {
- // APCu has a memory corruption bug when the serializer is set to 'default'.
- // See T120267, and upstream bug reports:
- // - https://github.com/krakjoe/apcu/issues/38
- // - https://github.com/krakjoe/apcu/issues/35
- // - https://github.com/krakjoe/apcu/issues/111
- $this->logger->warning(
- 'The APCu extension is loaded and the apc.serializer INI setting ' .
- 'is set to "default". This can cause memory corruption! ' .
- 'You should change apc.serializer to "php" instead. ' .
- 'See <https://github.com/krakjoe/apcu/issues/38>.'
- );
- $this->nativeSerialize = false;
- } else {
- $this->nativeSerialize = true;
- }
- }
+ const KEY_SUFFIX = ':3';
protected function doGet( $key, $flags = 0, &$casToken = null ) {
$casToken = null;
}
protected function serialize( $value ) {
- if ( !$this->nativeSerialize && !$this->isInteger( $value ) ) {
- $value = serialize( $value );
- }
- return $value;
+ return $this->isInteger( $value ) ? (int)$value : serialize( $value );
}
protected function unserialize( $value ) {
- if ( is_string( $value ) && !$this->nativeSerialize ) {
- $value = $this->isInteger( $value )
- ? intval( $value )
- : unserialize( $value );
- }
- return $value;
+ return $this->isInteger( $value ) ? (int)$value : unserialize( $value );
}
}
/**
* This is a wrapper for APCU's shared memory functions
*
+ * Use PHP serialization to avoid bugs and easily create CAS tokens.
+ * APCu has a memory corruption bug when the serializer is set to 'default'.
+ * See T120267, and upstream bug reports:
+ * - https://github.com/krakjoe/apcu/issues/38
+ * - https://github.com/krakjoe/apcu/issues/35
+ * - https://github.com/krakjoe/apcu/issues/111
+ *
* @ingroup Cache
*/
-class APCUBagOStuff extends APCBagOStuff {
+class APCUBagOStuff extends BagOStuff {
/**
- * Available parameters are:
- * - nativeSerialize: If true, pass objects to apcu_store(), and trust it
- * to serialize them correctly. If false, serialize
- * all values in PHP.
- *
- * @param array $params
+ * @var string String to append to each APC key. This may be changed
+ * whenever the handling of values is changed, to prevent existing code
+ * from encountering older values which it cannot handle.
*/
- public function __construct( array $params = [] ) {
- parent::__construct( $params );
- }
+ const KEY_SUFFIX = ':3';
protected function doGet( $key, $flags = 0, &$casToken = null ) {
$casToken = null;
return false;
}
}
+
+ protected function serialize( $value ) {
+ return $this->isInteger( $value ) ? (int)$value : serialize( $value );
+ }
+
+ protected function unserialize( $value ) {
+ return $this->isInteger( $value ) ? (int)$value : unserialize( $value );
+ }
}
}
unset( $this->conns[$connFreeKey][$i][$oldDomain] );
// Note that if $domain is an empty string, getDomainID() might not match it
- $this->conns[$connInUseKey][$i][$conn->getDomainId()] = $conn;
+ $this->conns[$connInUseKey][$i][$conn->getDomainID()] = $conn;
$this->connLogger->debug( __METHOD__ .
": reusing free connection from $oldDomain for $domain" );
break;
$ser = $image->getMetadata();
if ( $ser ) {
$metadata = unserialize( $ser );
-
- return $image->getWidth() * $image->getHeight() * $metadata['frameCount'];
+ if ( isset( $metadata['frameCount'] ) && $metadata['frameCount'] > 0 ) {
+ return $image->getWidth() * $image->getHeight() * $metadata['frameCount'];
+ } else {
+ return $image->getWidth() * $image->getHeight();
+ }
} else {
return $image->getWidth() * $image->getHeight();
}
$ser = $image->getMetadata();
if ( $ser ) {
$metadata = unserialize( $ser );
- if ( $metadata['frameCount'] > 1 ) {
+ if ( isset( $metadata['frameCount'] ) && $metadata['frameCount'] > 1 ) {
return true;
}
}
--- /dev/null
+<?php
+/**
+ * Base class for the output of file transformation methods.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Basic media transform error class
+ *
+ * @ingroup Media
+ */
+class MediaTransformError extends MediaTransformOutput {
+ /** @var Message */
+ private $msg;
+
+ function __construct( $msg, $width, $height /*, ... */ ) {
+ $args = array_slice( func_get_args(), 3 );
+ $this->msg = wfMessage( $msg )->params( $args );
+ $this->width = intval( $width );
+ $this->height = intval( $height );
+ $this->url = false;
+ $this->path = false;
+ }
+
+ function toHtml( $options = [] ) {
+ return "<div class=\"MediaTransformError\" style=\"" .
+ "width: {$this->width}px; height: {$this->height}px; display:inline-block;\">" .
+ $this->getHtmlMsg() .
+ "</div>";
+ }
+
+ function toText() {
+ return $this->msg->text();
+ }
+
+ function getHtmlMsg() {
+ return $this->msg->escaped();
+ }
+
+ function getMsg() {
+ return $this->msg;
+ }
+
+ function isError() {
+ return true;
+ }
+
+ function getHttpStatusCode() {
+ return 500;
+ }
+}
return $attribs;
}
}
-
-/**
- * Media transform output for images
- *
- * @ingroup Media
- */
-class ThumbnailImage extends MediaTransformOutput {
- private static $firstNonIconImageRendered = false;
-
- /**
- * Get a thumbnail object from a file and parameters.
- * If $path is set to null, the output file is treated as a source copy.
- * If $path is set to false, no output file will be created.
- * $parameters should include, as a minimum, (file) 'width' and 'height'.
- * It may also include a 'page' parameter for multipage files.
- *
- * @param File $file
- * @param string $url URL path to the thumb
- * @param string|bool $path Filesystem path to the thumb
- * @param array $parameters Associative array of parameters
- */
- function __construct( $file, $url, $path = false, $parameters = [] ) {
- # Previous parameters:
- # $file, $url, $width, $height, $path = false, $page = false
-
- $defaults = [
- 'page' => false,
- 'lang' => false
- ];
-
- if ( is_array( $parameters ) ) {
- $actualParams = $parameters + $defaults;
- } else {
- # Using old format, should convert. Later a warning could be added here.
- $numArgs = func_num_args();
- $actualParams = [
- 'width' => $path,
- 'height' => $parameters,
- 'page' => ( $numArgs > 5 ) ? func_get_arg( 5 ) : false
- ] + $defaults;
- $path = ( $numArgs > 4 ) ? func_get_arg( 4 ) : false;
- }
-
- $this->file = $file;
- $this->url = $url;
- $this->path = $path;
-
- # These should be integers when they get here.
- # If not, there's a bug somewhere. But let's at
- # least produce valid HTML code regardless.
- $this->width = round( $actualParams['width'] );
- $this->height = round( $actualParams['height'] );
-
- $this->page = $actualParams['page'];
- $this->lang = $actualParams['lang'];
- }
-
- /**
- * Return HTML <img ... /> tag for the thumbnail, will include
- * width and height attributes and a blank alt text (as required).
- *
- * @param array $options Associative array of options. Boolean options
- * should be indicated with a value of true for true, and false or
- * absent for false.
- *
- * alt HTML alt attribute
- * title HTML title attribute
- * desc-link Boolean, show a description link
- * file-link Boolean, show a file download link
- * valign vertical-align property, if the output is an inline element
- * img-class Class applied to the \<img\> tag, if there is such a tag
- * desc-query String, description link query params
- * override-width Override width attribute. Should generally not set
- * override-height Override height attribute. Should generally not set
- * no-dimensions Boolean, skip width and height attributes (useful if
- * set in CSS)
- * custom-url-link Custom URL to link to
- * custom-title-link Custom Title object to link to
- * custom target-link Value of the target attribute, for custom-target-link
- * parser-extlink-* Attributes added by parser for external links:
- * parser-extlink-rel: add rel="nofollow"
- * parser-extlink-target: link target, but overridden by custom-target-link
- *
- * For images, desc-link and file-link are implemented as a click-through. For
- * sounds and videos, they may be displayed in other ways.
- *
- * @throws MWException
- * @return string
- */
- function toHtml( $options = [] ) {
- global $wgPriorityHints, $wgElementTiming;
-
- if ( count( func_get_args() ) == 2 ) {
- throw new MWException( __METHOD__ . ' called in the old style' );
- }
-
- $alt = $options['alt'] ?? '';
-
- $query = $options['desc-query'] ?? '';
-
- $attribs = [
- 'alt' => $alt,
- 'src' => $this->url,
- 'decoding' => 'async',
- ];
-
- $elementTimingName = 'thumbnail';
-
- if ( $wgPriorityHints
- && !self::$firstNonIconImageRendered
- && $this->width * $this->height > 100 * 100 ) {
- self::$firstNonIconImageRendered = true;
-
- $attribs['importance'] = 'high';
- $elementTimingName = 'thumbnail-high';
- }
-
- if ( $wgElementTiming ) {
- $attribs['elementtiming'] = $elementTimingName;
- }
-
- if ( !empty( $options['custom-url-link'] ) ) {
- $linkAttribs = [ 'href' => $options['custom-url-link'] ];
- if ( !empty( $options['title'] ) ) {
- $linkAttribs['title'] = $options['title'];
- }
- if ( !empty( $options['custom-target-link'] ) ) {
- $linkAttribs['target'] = $options['custom-target-link'];
- } elseif ( !empty( $options['parser-extlink-target'] ) ) {
- $linkAttribs['target'] = $options['parser-extlink-target'];
- }
- if ( !empty( $options['parser-extlink-rel'] ) ) {
- $linkAttribs['rel'] = $options['parser-extlink-rel'];
- }
- } elseif ( !empty( $options['custom-title-link'] ) ) {
- /** @var Title $title */
- $title = $options['custom-title-link'];
- $linkAttribs = [
- 'href' => $title->getLinkURL(),
- 'title' => empty( $options['title'] ) ? $title->getFullText() : $options['title']
- ];
- } elseif ( !empty( $options['desc-link'] ) ) {
- $linkAttribs = $this->getDescLinkAttribs(
- empty( $options['title'] ) ? null : $options['title'],
- $query
- );
- } elseif ( !empty( $options['file-link'] ) ) {
- $linkAttribs = [ 'href' => $this->file->getUrl() ];
- } else {
- $linkAttribs = false;
- if ( !empty( $options['title'] ) ) {
- $attribs['title'] = $options['title'];
- }
- }
-
- if ( empty( $options['no-dimensions'] ) ) {
- $attribs['width'] = $this->width;
- $attribs['height'] = $this->height;
- }
- if ( !empty( $options['valign'] ) ) {
- $attribs['style'] = "vertical-align: {$options['valign']}";
- }
- if ( !empty( $options['img-class'] ) ) {
- $attribs['class'] = $options['img-class'];
- }
- if ( isset( $options['override-height'] ) ) {
- $attribs['height'] = $options['override-height'];
- }
- if ( isset( $options['override-width'] ) ) {
- $attribs['width'] = $options['override-width'];
- }
-
- // Additional densities for responsive images, if specified.
- // If any of these urls is the same as src url, it'll be excluded.
- $responsiveUrls = array_diff( $this->responsiveUrls, [ $this->url ] );
- if ( !empty( $responsiveUrls ) ) {
- $attribs['srcset'] = Html::srcSet( $responsiveUrls );
- }
-
- Hooks::run( 'ThumbnailBeforeProduceHTML', [ $this, &$attribs, &$linkAttribs ] );
-
- return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) );
- }
-}
-
-/**
- * Basic media transform error class
- *
- * @ingroup Media
- */
-class MediaTransformError extends MediaTransformOutput {
- /** @var Message */
- private $msg;
-
- function __construct( $msg, $width, $height /*, ... */ ) {
- $args = array_slice( func_get_args(), 3 );
- $this->msg = wfMessage( $msg )->params( $args );
- $this->width = intval( $width );
- $this->height = intval( $height );
- $this->url = false;
- $this->path = false;
- }
-
- function toHtml( $options = [] ) {
- return "<div class=\"MediaTransformError\" style=\"" .
- "width: {$this->width}px; height: {$this->height}px; display:inline-block;\">" .
- $this->getHtmlMsg() .
- "</div>";
- }
-
- function toText() {
- return $this->msg->text();
- }
-
- function getHtmlMsg() {
- return $this->msg->escaped();
- }
-
- function getMsg() {
- return $this->msg;
- }
-
- function isError() {
- return true;
- }
-
- function getHttpStatusCode() {
- return 500;
- }
-}
-
-/**
- * Shortcut class for parameter validation errors
- *
- * @ingroup Media
- */
-class TransformParameterError extends MediaTransformError {
- function __construct( $params ) {
- parent::__construct( 'thumbnail_error',
- max( $params['width'] ?? 0, 120 ),
- max( $params['height'] ?? 0, 120 ),
- wfMessage( 'thumbnail_invalid_params' )
- );
- }
-
- function getHttpStatusCode() {
- return 400;
- }
-}
-
-/**
- * Shortcut class for parameter file size errors
- *
- * @ingroup Media
- * @since 1.25
- */
-class TransformTooBigImageAreaError extends MediaTransformError {
- function __construct( $params, $maxImageArea ) {
- $msg = wfMessage( 'thumbnail_toobigimagearea' );
- $msg->params(
- $msg->getLanguage()->formatComputingNumbers( $maxImageArea, 1000, "size-$1pixel" )
- );
-
- parent::__construct( 'thumbnail_error',
- max( $params['width'] ?? 0, 120 ),
- max( $params['height'] ?? 0, 120 ),
- $msg
- );
- }
-
- function getHttpStatusCode() {
- return 400;
- }
-}
--- /dev/null
+<?php
+/**
+ * Base class for the output of file transformation methods.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Media transform output for images
+ *
+ * @ingroup Media
+ */
+class ThumbnailImage extends MediaTransformOutput {
+ private static $firstNonIconImageRendered = false;
+
+ /**
+ * Get a thumbnail object from a file and parameters.
+ * If $path is set to null, the output file is treated as a source copy.
+ * If $path is set to false, no output file will be created.
+ * $parameters should include, as a minimum, (file) 'width' and 'height'.
+ * It may also include a 'page' parameter for multipage files.
+ *
+ * @param File $file
+ * @param string $url URL path to the thumb
+ * @param string|bool $path Filesystem path to the thumb
+ * @param array $parameters Associative array of parameters
+ */
+ function __construct( $file, $url, $path = false, $parameters = [] ) {
+ # Previous parameters:
+ # $file, $url, $width, $height, $path = false, $page = false
+
+ $defaults = [
+ 'page' => false,
+ 'lang' => false
+ ];
+
+ if ( is_array( $parameters ) ) {
+ $actualParams = $parameters + $defaults;
+ } else {
+ # Using old format, should convert. Later a warning could be added here.
+ $numArgs = func_num_args();
+ $actualParams = [
+ 'width' => $path,
+ 'height' => $parameters,
+ 'page' => ( $numArgs > 5 ) ? func_get_arg( 5 ) : false
+ ] + $defaults;
+ $path = ( $numArgs > 4 ) ? func_get_arg( 4 ) : false;
+ }
+
+ $this->file = $file;
+ $this->url = $url;
+ $this->path = $path;
+
+ # These should be integers when they get here.
+ # If not, there's a bug somewhere. But let's at
+ # least produce valid HTML code regardless.
+ $this->width = round( $actualParams['width'] );
+ $this->height = round( $actualParams['height'] );
+
+ $this->page = $actualParams['page'];
+ $this->lang = $actualParams['lang'];
+ }
+
+ /**
+ * Return HTML <img ... /> tag for the thumbnail, will include
+ * width and height attributes and a blank alt text (as required).
+ *
+ * @param array $options Associative array of options. Boolean options
+ * should be indicated with a value of true for true, and false or
+ * absent for false.
+ *
+ * alt HTML alt attribute
+ * title HTML title attribute
+ * desc-link Boolean, show a description link
+ * file-link Boolean, show a file download link
+ * valign vertical-align property, if the output is an inline element
+ * img-class Class applied to the \<img\> tag, if there is such a tag
+ * desc-query String, description link query params
+ * override-width Override width attribute. Should generally not set
+ * override-height Override height attribute. Should generally not set
+ * no-dimensions Boolean, skip width and height attributes (useful if
+ * set in CSS)
+ * custom-url-link Custom URL to link to
+ * custom-title-link Custom Title object to link to
+ * custom target-link Value of the target attribute, for custom-target-link
+ * parser-extlink-* Attributes added by parser for external links:
+ * parser-extlink-rel: add rel="nofollow"
+ * parser-extlink-target: link target, but overridden by custom-target-link
+ *
+ * For images, desc-link and file-link are implemented as a click-through. For
+ * sounds and videos, they may be displayed in other ways.
+ *
+ * @throws MWException
+ * @return string
+ */
+ function toHtml( $options = [] ) {
+ global $wgPriorityHints, $wgElementTiming;
+
+ if ( count( func_get_args() ) == 2 ) {
+ throw new MWException( __METHOD__ . ' called in the old style' );
+ }
+
+ $alt = $options['alt'] ?? '';
+
+ $query = $options['desc-query'] ?? '';
+
+ $attribs = [
+ 'alt' => $alt,
+ 'src' => $this->url,
+ 'decoding' => 'async',
+ ];
+
+ $elementTimingName = 'thumbnail';
+
+ if ( $wgPriorityHints
+ && !self::$firstNonIconImageRendered
+ && $this->width * $this->height > 100 * 100 ) {
+ self::$firstNonIconImageRendered = true;
+
+ $attribs['importance'] = 'high';
+ $elementTimingName = 'thumbnail-high';
+ }
+
+ if ( $wgElementTiming ) {
+ $attribs['elementtiming'] = $elementTimingName;
+ }
+
+ if ( !empty( $options['custom-url-link'] ) ) {
+ $linkAttribs = [ 'href' => $options['custom-url-link'] ];
+ if ( !empty( $options['title'] ) ) {
+ $linkAttribs['title'] = $options['title'];
+ }
+ if ( !empty( $options['custom-target-link'] ) ) {
+ $linkAttribs['target'] = $options['custom-target-link'];
+ } elseif ( !empty( $options['parser-extlink-target'] ) ) {
+ $linkAttribs['target'] = $options['parser-extlink-target'];
+ }
+ if ( !empty( $options['parser-extlink-rel'] ) ) {
+ $linkAttribs['rel'] = $options['parser-extlink-rel'];
+ }
+ } elseif ( !empty( $options['custom-title-link'] ) ) {
+ /** @var Title $title */
+ $title = $options['custom-title-link'];
+ $linkAttribs = [
+ 'href' => $title->getLinkURL(),
+ 'title' => empty( $options['title'] ) ? $title->getFullText() : $options['title']
+ ];
+ } elseif ( !empty( $options['desc-link'] ) ) {
+ $linkAttribs = $this->getDescLinkAttribs(
+ empty( $options['title'] ) ? null : $options['title'],
+ $query
+ );
+ } elseif ( !empty( $options['file-link'] ) ) {
+ $linkAttribs = [ 'href' => $this->file->getUrl() ];
+ } else {
+ $linkAttribs = false;
+ if ( !empty( $options['title'] ) ) {
+ $attribs['title'] = $options['title'];
+ }
+ }
+
+ if ( empty( $options['no-dimensions'] ) ) {
+ $attribs['width'] = $this->width;
+ $attribs['height'] = $this->height;
+ }
+ if ( !empty( $options['valign'] ) ) {
+ $attribs['style'] = "vertical-align: {$options['valign']}";
+ }
+ if ( !empty( $options['img-class'] ) ) {
+ $attribs['class'] = $options['img-class'];
+ }
+ if ( isset( $options['override-height'] ) ) {
+ $attribs['height'] = $options['override-height'];
+ }
+ if ( isset( $options['override-width'] ) ) {
+ $attribs['width'] = $options['override-width'];
+ }
+
+ // Additional densities for responsive images, if specified.
+ // If any of these urls is the same as src url, it'll be excluded.
+ $responsiveUrls = array_diff( $this->responsiveUrls, [ $this->url ] );
+ if ( !empty( $responsiveUrls ) ) {
+ $attribs['srcset'] = Html::srcSet( $responsiveUrls );
+ }
+
+ Hooks::run( 'ThumbnailBeforeProduceHTML', [ $this, &$attribs, &$linkAttribs ] );
+
+ return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) );
+ }
+}
--- /dev/null
+<?php
+/**
+ * Base class for the output of file transformation methods.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Shortcut class for parameter validation errors
+ *
+ * @ingroup Media
+ */
+class TransformParameterError extends MediaTransformError {
+ function __construct( $params ) {
+ parent::__construct( 'thumbnail_error',
+ max( $params['width'] ?? 0, 120 ),
+ max( $params['height'] ?? 0, 120 ),
+ wfMessage( 'thumbnail_invalid_params' )
+ );
+ }
+
+ function getHttpStatusCode() {
+ return 400;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Base class for the output of file transformation methods.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Shortcut class for parameter file size errors
+ *
+ * @ingroup Media
+ * @since 1.25
+ */
+class TransformTooBigImageAreaError extends MediaTransformError {
+ function __construct( $params, $maxImageArea ) {
+ $msg = wfMessage( 'thumbnail_toobigimagearea' );
+ $msg->params(
+ $msg->getLanguage()->formatComputingNumbers( $maxImageArea, 1000, "size-$1pixel" )
+ );
+
+ parent::__construct( 'thumbnail_error',
+ max( $params['width'] ?? 0, 120 ),
+ max( $params['height'] ?? 0, 120 ),
+ $msg
+ );
+ }
+
+ function getHttpStatusCode() {
+ return 400;
+ }
+}
return $blobs;
}
- /**
- * @deprecated since 1.27 Use getBlobs() instead
- * @return array
- */
- public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
- return $this->getBlobs( $modules, $lang );
- }
-
/**
* @since 1.27
* @param ResourceLoaderModule $module
}
}
}
-
-/**
- * Dummy class to be used when non-supported Database engine is present.
- * @todo FIXME: Dummy class should probably try something at least mildly useful,
- * such as a LIKE search through titles.
- * @ingroup Search
- */
-class SearchEngineDummy extends SearchEngine {
- // no-op
-}
--- /dev/null
+<?php
+/**
+ * Dummy search engine
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Search
+ */
+
+/**
+ * Dummy class to be used when non-supported Database engine is present.
+ * @todo FIXME: Dummy class should probably try something at least mildly useful,
+ * such as a LIKE search through titles.
+ * @ingroup Search
+ */
+class SearchEngineDummy extends SearchEngine {
+ // no-op
+}
$this->load();
// TODO: This performs database actions on GET request, which is going to
// be a problem for our multi-datacenter work.
- if ( !is_null( $request->getVal( 'nsRemember' ) ) ) {
+ if ( $request->getCheck( 'nsRemember' ) ) {
$this->saveNamespaces();
// Remove the token from the URL to prevent the user from inadvertently
// exposing it (e.g. by pasting it into a public wiki page) or undoing
}
$this->searchEngineType = $request->getVal( 'srbackend' );
- if (
- !$request->getVal( 'fulltext' ) &&
- $request->getVal( 'offset' ) === null
- ) {
+ if ( !$request->getVal( 'fulltext' ) && !$request->getCheck( 'offset' ) ) {
$url = $this->goResult( $term );
if ( $url !== null ) {
// successful 'go'
$this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $user->isAllowed( 'suppressrevision' );
$this->mToken = $request->getVal( 'token' );
- if ( $this->isAllowed( 'undelete' ) && !$user->isBlocked() ) {
+ $block = $user->getBlock();
+ if ( $this->isAllowed( 'undelete' ) && !( $block && $block->isSitewide() ) ) {
$this->mAllowed = true; // user can restore
$this->mCanView = true; // user can view content
} elseif ( $this->isAllowed( 'deletedtext' ) ) {
Message::rawParam( $link ), $time,
Message::rawParam( $userLink ), $d, $t
);
- $out->addHtml( '</div>' );
+ $out->addHTML( '</div>' );
if ( !Hooks::run( 'UndeleteShowRevision', [ $this->mTargetObj, $rev ] ) ) {
return;
switch ( $restriction->getType() ) {
case PageRestriction::TYPE:
if ( $restriction->getTitle() ) {
- $items[$restriction->getType()][] = HTML::rawElement(
+ $items[$restriction->getType()][] = Html::rawElement(
'li',
[],
Linker::link( $restriction->getTitle() )
: $this->getLanguage()->getFormattedNsText(
$restriction->getValue()
);
- $items[$restriction->getType()][] = HTML::rawElement(
+ $items[$restriction->getType()][] = Html::rawElement(
'li',
[],
Linker::link(
* is the execute() method. See docs/maintenance.txt for more info
* and a quick demo of how to use it.
*
+ * Terminology:
+ * params: registry of named values that may be passed to the script
+ * arg list: registry of positional values that may be passed to the script
+ * options: passed param values
+ * args: passed positional values
+ *
+ * In the command:
+ * mwscript somescript.php --foo=bar baz
+ * foo is a param
+ * bar is the option value of the option for param foo
+ * baz is the arg value at index 0 in the arg list
+ *
* @since 1.16
* @ingroup Maintenance
*/
// Const for getStdin()
const STDIN_ALL = 'all';
- // This is the desired params
+ // Array of desired/allowed params
protected $mParams = [];
// Array of mapping short parameters to long ones
protected $mShortParamsMap = [];
- // Array of desired args
+ // Array of desired/allowed args
protected $mArgList = [];
// This is the list of options that were actually passed
}
$this->loadParamsAndArgs();
- $this->maybeHelp();
# Set the memory limit
# Note we need to set it again later in cache LocalSettings changed it
while ( ob_get_level() > 0 ) {
ob_end_flush();
}
-
- $this->validateParamsAndArgs();
}
/**
/**
* Run some validation checks on the params, etc
*/
- protected function validateParamsAndArgs() {
+ public function validateParamsAndArgs() {
$die = false;
# Check to make sure we've got all the required options
foreach ( $this->mParams as $opt => $info ) {
}
}
- if ( $die ) {
- $this->maybeHelp( true );
- }
+ $this->maybeHelp( $die );
}
/**
// This avoids having long running scripts just OOM and lose all the updates.
$maintenance->setAgentAndTriggers();
+$maintenance->validateParamsAndArgs();
+
// Do the work
$success = $maintenance->execute();
$this->addOption( 'format', implode( ', ', self::$outFormats ), false, true );
}
- protected function validateParamsAndArgs() {
+ public function validateParamsAndArgs() {
$error_out = false;
# Get the format and make sure it is set to a valid default value
$response['reached'] === 'memory-limit'
) {
// If job queue is empty, output it
- if ( $response['jobs'] === [] ) {
+ if ( !$outputJSON && $response['jobs'] === [] ) {
$this->output( "Job queue is empty.\n" );
}
break;
'manualRecache' => false,
];
}
+
+ public function validateParamsAndArgs() {
+ // Allow extensions to add additional params.
+ $params = [];
+ Hooks::run( 'MaintenanceUpdateAddParams', [ &$params ] );
+ foreach ( $params as $name => $param ) {
+ $this->addOption(
+ $name,
+ $param['desc'],
+ $param['require'] ?? false,
+ $param['withArg'] ?? false,
+ $param['shortName'] ?? false,
+ $param['multiOccurrence'] ?? false
+ );
+ }
+
+ parent::validateParamsAndArgs();
+ }
}
$maintClass = UpdateMediaWiki::class;
$session = array();
}
- if ( $request->getVal( 'uselang' ) !== null ) {
+ if ( $request->getCheck( 'uselang' ) ) {
$langCode = $request->getVal( 'uselang' );
} elseif ( isset( $session['settings']['_UserLang'] ) ) {
$langCode = $session['settings']['_UserLang'];
/* MediaWiki Special pages */
'mediawiki.rcfilters.filters.base.styles' => [
- 'styles' => [
- 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less',
+ 'skinStyles' => [
+ 'default' => 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less',
],
],
'mediawiki.rcfilters.highlightCircles.seenunseen.styles' => [
- 'styles' => [
- 'resources/src/mediawiki.rcfilters/' .
- 'styles/mw.rcfilters.ui.ChangesListWrapperWidget.highlightCircles.seenunseen.less',
+ 'skinStyles' => [
+ 'default' => [
+ 'resources/src/mediawiki.rcfilters/' .
+ 'styles/mw.rcfilters.ui.ChangesListWrapperWidget.highlightCircles.seenunseen.less',
+ ],
],
],
'mediawiki.rcfilters.filters.dm' => [
+/*!
+ * Styles for Special:ApiSandbox
+ */
.client-js .mw-apisandbox-nojs {
display: none;
}
-/* Special:Block styles */
+/*!
+ * Styles for Special:Block
+ */
-// OOUIHTMLForm styles
-@ooui-font-size-browser: 16; // Assumed browser default of `16px`
-@ooui-font-size-base: 0.875em; // Equals `14px` at browser default of `16px`
+// OOUIHTMLForm specifics
+@ooui-font-size-browser: 16; // Assumed browser default of `16px`.
+@ooui-font-size-base: 0.875em; // Equals `14px` at browser default of `16px`.
-@ooui-spacing-radio-label: 26 / @ooui-font-size-browser / @ooui-font-size-base; // Equals `1.85714286em`≈`26px`
+@ooui-spacing-radio-label: 26 / @ooui-font-size-browser / @ooui-font-size-base; // Equals `1.85714286em`≈`26px`.
body.mw-special-Block {
.mw-block-editing-restriction.oo-ui-fieldLayout {
+/*!
+ * Styles for Special:ComparePages
+ */
@import 'mediawiki.mixins';
.mw-special-ComparePages .mw-htmlform-ooui-wrapper {
/*!
- * Styling for Special:EditTags and action=editchangetags
+ * Styles for Special:EditTags and action=editchangetags
*/
#mw-edittags-tags-selector td {
vertical-align: top;
/*!
- * Styling for Special:NewPages
+ * Styles for Special:NewPages
*/
// OOUIHTMLForm styles
-/* Distinguish actual data from information about it being hidden visually */
+/*!
+ * Styles for Special:PagesWithProp
+ */
+
+/* Distinguish actual data from information about it being hidden visually. */
.prop-value-hidden {
font-style: italic;
}
-/* Special:AllMessages */
+/*!
+ * Styles shared across various special pages.
+ */
@import 'mediawiki.mixins';
+/* Special:AllMessages */
/* Visually hide repeating text, but leave in for better form navigation on screen readers */
.mw-special-Allmessages .mw-htmlform-ooui .oo-ui-fieldsetLayout:first-child .oo-ui-fieldsetLayout-header {
.mixin-screen-reader-text();
/*!
- * Styling for Special:Upload
+ * Styles for Special:Upload
*/
.mw-destfile-warning {
border: 1px solid #fde29b;
/*!
- * Styling for Special:UserRights
+ * Styles for Special:UserRights
*/
.mw-userrights-nested {
margin-left: 1.2em;
/*!
- * Styling for elements generated by JavaScript on Special:Watchlist
+ * Styles for elements generated by JavaScript on Special:Watchlist
*/
.mw-changelist-line-inner-unwatched {
text-decoration: line-through;
var userGroups = mw.config.get( 'wgUserGroups', [] );
// Uses promise for backwards compatibility
- return $.Deferred().resolve( userGroups ).done( callback );
+ return $.Deferred().resolve( userGroups ).then( callback );
},
/**
return getUserInfo().then(
function ( userInfo ) { return userInfo.rights; },
function () { return []; }
- ).done( callback );
+ ).then( callback );
}
} );
[
'page',
'revision',
+ 'comment',
'ip_changes',
'text',
'archive',
$this->setService( 'MainWANObjectCache', $cache );
$db = wfGetDB( DB_MASTER );
+ $now = 1553893742;
+ $cache->setMockTime( $now );
+
// Get a fresh revision to use during testing
$this->testPage->doEditContent( new WikitextContent( __METHOD__ ), __METHOD__ );
$rev = $this->testPage->getRevision();
$cache->delete( $key, WANObjectCache::HOLDOFF_NONE );
$this->assertFalse( $cache->get( $key ) );
+ ++$now;
+
// Get the new revision and make sure it is in the cache and correct
$newRev = Revision::newKnownCurrent( $db, $rev->getPage(), $rev->getId() );
$this->assertRevEquals( $rev, $newRev );
* @covers WikiMap::getCurrentWikiDbDomain()
*/
public function testIsCurrentWikiDomain() {
- $this->assertTrue( WikiMap::isCurrentWikiDbDomain( wfWikiID() ) );
+ $this->setMwGlobals( 'wgDBmwschema', 'mediawiki' );
- $localDomain = DatabaseDomain::newFromId( wfWikiID() );
+ $localDomain = WikiMap::getCurrentWikiDbDomain()->getId();
+ $this->assertTrue( WikiMap::isCurrentWikiDbDomain( $localDomain ) );
+
+ $localDomain = DatabaseDomain::newFromId( $localDomain );
$domain1 = new DatabaseDomain(
$localDomain->getDatabase(), 'someschema', $localDomain->getTablePrefix() );
$domain2 = new DatabaseDomain(
$localDomain->getDatabase(), null, $localDomain->getTablePrefix() );
- $this->assertTrue( WikiMap::isCurrentWikiDbDomain( $domain1 ), 'Schema ignored' );
- $this->assertTrue( WikiMap::isCurrentWikiDbDomain( $domain2 ), 'Schema ignored' );
+ $this->assertFalse( WikiMap::isCurrentWikiDbDomain( $domain1 ), 'Schema not ignored' );
+ $this->assertFalse( WikiMap::isCurrentWikiDbDomain( $domain2 ), 'Null schema not ignored' );
$this->assertTrue( WikiMap::isCurrentWikiDbDomain( WikiMap::getCurrentWikiDbDomain() ) );
}
private function newJobQueue() {
return JobQueue::factory( [
'class' => JobQueueMemory::class,
- 'wiki' => wfWikiID(),
+ 'domain' => WikiMap::getCurrentWikiDbDomain()->getId(),
'type' => 'null',
] );
}
$baseConfig = [ 'class' => JobQueueDBSingle::class ];
}
$baseConfig['type'] = 'null';
- $baseConfig['wiki'] = wfWikiID();
+ $baseConfig['domain'] = WikiMap::getCurrentWikiDbDomain()->getId();
$variants = [
'queueRand' => [ 'order' => 'random', 'claimTTL' => 0 ],
'queueRandTTL' => [ 'order' => 'random', 'claimTTL' => 10 ],
$this->markTestSkipped( $desc );
}
$this->assertEquals( wfWikiID(), $queue->getWiki(), "Proper wiki ID ($desc)" );
- $this->assertEquals( wfWikiID(), $queue->getDomain(), "Proper wiki ID ($desc)" );
+ $this->assertEquals(
+ WikiMap::getCurrentWikiDbDomain()->getId(),
+ $queue->getDomain(),
+ "Proper wiki ID ($desc)" );
}
/**
$this->assertEquals( $n, $calls );
}
- /**
- * @covers BagOStuff::merge
- * @dataProvider provideTestMerge_fork
- */
- public function testMerge_fork( $exists, $childWins, $resCAS ) {
- $key = $this->cache->makeKey( self::TEST_KEY );
- $pCallback = function ( BagOStuff $cache, $key, $oldVal ) {
- return ( $oldVal === false ) ? 'init-parent' : $oldVal . '-merged-parent';
- };
- $cCallback = function ( BagOStuff $cache, $key, $oldVal ) {
- return ( $oldVal === false ) ? 'init-child' : $oldVal . '-merged-child';
- };
-
- if ( $exists ) {
- $this->cache->set( $key, 'x', 5 );
- }
-
- /*
- * Test concurrent merges by forking this process, if:
- * - not manually called with --use-bagostuff
- * - pcntl_fork is supported by the system
- * - cache type will correctly support calls over forks
- */
- $fork = (bool)$this->getCliArg( 'use-bagostuff' );
- $fork &= function_exists( 'pcntl_fork' );
- $fork &= !$this->cache instanceof HashBagOStuff;
- $fork &= !$this->cache instanceof EmptyBagOStuff;
- $fork &= !$this->cache instanceof MultiWriteBagOStuff;
- if ( $fork ) {
- $pid = null;
- // Function to start merge(), run another merge() midway through, then finish
- $func = function ( $cache, $key, $cur ) use ( $pCallback, $cCallback, &$pid ) {
- $pid = pcntl_fork();
- if ( $pid == -1 ) {
- return false;
- } elseif ( $pid ) {
- pcntl_wait( $status );
-
- return $pCallback( $cache, $key, $cur );
- } else {
- $this->cache->merge( $key, $cCallback, 0, 1 );
- // Bail out of the outer merge() in the child process since it does not
- // need to attempt to write anything. Success is checked by the parent.
- parent::tearDown(); // avoid phpunit notices
- exit;
- }
- };
-
- // attempt a merge - this should fail
- $merged = $this->cache->merge( $key, $func, 0, 1 );
-
- if ( $pid == -1 ) {
- return; // can't fork, ignore this test...
- }
-
- // merge has failed because child process was merging (and we only attempted once)
- $this->assertEquals( !$childWins, $merged );
- $this->assertEquals( $this->cache->get( $key ), $resCAS );
- } else {
- $this->markTestSkipped( 'No pcntl methods available' );
- }
- }
-
- function provideTestMerge_fork() {
- return [
- // (already exists, child wins CAS, result of CAS)
- [ false, true, 'init-child' ],
- [ true, true, 'x-merged-child' ]
- ];
- }
-
/**
* @covers BagOStuff::changeTTL
*/
QUnit.module( 'mediawiki.user', QUnit.newMwEnvironment( {
setup: function () {
this.server = this.sandbox.useFakeServer();
+ this.server.respondImmediately = true;
// Cannot stub by simple assignment because read-only.
// Instead, stub in tests by using 'delete', and re-create
// in teardown using the original descriptor (including its
assert.strictEqual( mw.user.id(), 'John', 'user.id()' );
} );
- QUnit.test( 'getUserInfo', function ( assert ) {
+ QUnit.test( 'getGroups (callback)', function ( assert ) {
+ var done = assert.async();
mw.config.set( 'wgUserGroups', [ '*', 'user' ] );
mw.user.getGroups( function ( groups ) {
assert.deepEqual( groups, [ '*', 'user' ], 'Result' );
+ done();
} );
+ } );
+
+ QUnit.test( 'getGroups (Promise)', function ( assert ) {
+ mw.config.set( 'wgUserGroups', [ '*', 'user' ] );
+
+ return mw.user.getGroups().then( function ( groups ) {
+ assert.deepEqual( groups, [ '*', 'user' ], 'Result' );
+ } );
+ } );
+
+ QUnit.test( 'getRights (callback)', function ( assert ) {
+ var done = assert.async();
+
+ this.server.respond( [ 200, { 'Content-Type': 'application/json' },
+ '{ "query": { "userinfo": { "groups": [ "unused" ], "rights": [ "read", "edit", "createtalk" ] } } }'
+ ] );
mw.user.getRights( function ( rights ) {
assert.deepEqual( rights, [ 'read', 'edit', 'createtalk' ], 'Result (callback)' );
+ done();
} );
+ } );
- mw.user.getRights().done( function ( rights ) {
- assert.deepEqual( rights, [ 'read', 'edit', 'createtalk' ], 'Result (promise)' );
- } );
+ QUnit.test( 'getRights (Promise)', function ( assert ) {
+ this.server.respond( [ 200, { 'Content-Type': 'application/json' },
+ '{ "query": { "userinfo": { "groups": [ "unused" ], "rights": [ "read", "edit", "createtalk" ] } } }'
+ ] );
- this.server.respondWith( /meta=userinfo/, function ( request ) {
- request.respond( 200, { 'Content-Type': 'application/json' },
- '{ "query": { "userinfo": { "groups": [ "unused" ], "rights": [ "read", "edit", "createtalk" ] } } }'
- );
+ return mw.user.getRights().then( function ( rights ) {
+ assert.deepEqual( rights, [ 'read', 'edit', 'createtalk' ], 'Result (promise)' );
} );
-
- this.server.respond();
} );
QUnit.test( 'generateRandomSessionId', function ( assert ) {
it( 'should offer a way to cancel rollbacks', function () {
HistoryPage.rollback.click();
+ browser.pause( 300 );
HistoryPage.rollbackConfirmableNo.click();
browser.pause( 500 );